技巧15 “保存游戏”的方式:廉价的源代码管理

如果你曾经开发过任意类型的软件,那么你应该不止一次抱怨过:“我敢肯定在此之前它运行得好好的!”也许原话还没有这么淡定。由于无法将系统还原到一个已知的良好(或者也许只有“更好”)状态,只能赶紧强行修改(hack)代码以赶上最后期限或修复漏洞,这也是许多人敲碎键盘累死累活的原因。

源代码管理极大地改善了这一点,然而在特殊情况下还是存在以下两个问题:

  • 源代码仓库可能无法反映出“工作”环境下文件系统的状态;
  • 暂时还不想把代码提交上去。

第一个问题比第二个问题更明显一些。尽管像Git这样的现代化源代码管理工具可以轻松地创建一次性的本地分支,但是抓取整个开发环境文件系统的状态并不是源代码管理的初衷。

Docker通过它的提交(commit)功能提供了一个廉价、快捷的方式来保存容器的开发环境文件系统状态,而这正是接下来要探讨的内容。

问题

想要保存开发环境的状态。

解决方案

定期提交容器以便可以在需要的时候恢复到提交时的状态。

我们不妨假设用户想要修改第1章里的to-do应用。ToDo公司的CEO对这个应用不是很满意,并且想把浏览器上显示的标题从“Swarm+React - TodoMVC”改为“ToDoCorp’s ToDo App”。

用户拿不准要怎么实现这一点,也许会想要先把应用运行起来,然后通过修改文件来进行实验,看看到底会发生什么,如代码清单3-16所示。

代码清单3-16 在一个终端里调试应用程序

$ docker run -d -p 8000:8000 --name todobug1 dockerinpractice/todoapp
 3c3d5d3ffd70d17e7e47e90801af7d12d6fc0b8b14a8b33131fc708423ee4372
$ docker exec -i -t todobug1 /bin/bash

上述 docker run 命令在一个容器里以后台模式( -d )启动了to-do应用,将容器的8000端口映射到了宿主机上的8000端口( -p 8000:8000 ),为方便引用起见,将其命名为todobug1( --name todobug1 ),然后返回了该容器的ID。该容器启动时执行的命令默认会是我们构建的 dockerinpractice/todoapp 镜像在构建时指定的命令,这一镜像也可以在Docker Hub上找到。

第二条命令将会在正在运行的容器里启动 /bin/bash 。这里用到的是名为 todobug1 的容器,不过用户也可以使用其原本的容器ID。 -i 意味着这条 exec 命令以交互模式执行,而 -t 确保 exec 将会按照一个终端预期的那样工作。

如今我们已经在容器里了,那么,实验的第一步便是安装一个编辑器。我们更喜欢vim,所以采用了如下命令:

apt-get update
apt-get install vim

一番努力之后,我们意识到需要修改的文件是local.html。因此,我们将该文件的第5行改成了如下内容:

<title>ToDoCorp's ToDo App</title>

这时收到通知说CEO可能希望标题使用小写,因为她听说这样看上去会更时尚些。这两种方式我们都想准备好,所以我们选择先提交现有成果。在另外一个终端下执行代码清单3-17所示的命令。

代码清单3-17 提交容器状态

$ docker commit todobug1  ⇽--- 把之前创建的容器转成镜像
 ca76b45144f2cb31fda6a31e55f784c93df8c9d4c96bbeacd73cad9cd55d2970  ⇽--- 刚提交的容器的新镜像ID

如今已经将容器提交成镜像,并且可以在之后运行它。

注意

提交一个容器只会保存在提交那个时刻容器的文件系统的状态,而不是进程。记住,Docker容器不是虚拟机。如果环境的状态依赖于一些正在运行的进程的状态,而这些进程不能通过标准文件恢复的话,这个技巧将无法帮助用户保存所需的那个状态。在这种情况下,用户也许得想办法看怎样才能恢复开发环境里进程的状态。

紧接着,将local.html的内容修改成另外一个可能要求的值:

<title>todocorp's todo app</title>

然后再次提交:

$ docker commit todobug1
 071f6a36c23a19801285b82eafc99333c76f63ea0aa0b44902c6bae482a6e036

现在拥有两个镜像的ID(在这个例子里是ca76b45144f2cb31fda6a31e55f784c93df8c9d4c96bb eac d73cad9cd55d2970和071f6a36c23a19801285b82eafc99333c76f63ea0aa0b44902c6bae482a6e036,但是读者的可能会不一样)代表两种选择。当CEO来评估其想要的方案时,可以把两个镜像都运行起来,然后让她决定要提交哪一个。

可以通过打开新的终端然后执行代码清单3-18所示的命令来实现这一点。

代码清单3-18 基于两个已经提交的镜像运行容器

$ docker run -p 8001:8000 \
 ca76b45144f2cb31fda6a31e55f784c93df8c9d4c96bbeacd73cad9cd55d2970  ⇽--- 将容器的8000端口映射到宿主机的8001端口,然后指定小写的那个镜像的ID
$ docker run -p 8002:8000 \
 071f6a36c23a19801285b82eafc99333c76f63ea0aa0b44902c6bae482a6e036  ⇽--- 将容器的8000端口映射到宿主机的8002端口,然后指定大写的那个镜像的ID

这样一来,便可以在http://localhost:8001上展示大写的方案,在http://localhost:8002上呈现小写的方案。

注意

容器的任何外部依赖(如数据库、Docker卷或其他被调用的服务)均不会在提交时存储。这项技巧没有任何外部依赖性,因此用户不必担心这一点。

讨论

本技巧演示了 docker commit 的功能,以及如何在开发工作流中使用它。Docker用户往往倾向于作为正式的 commit-tag-push 工作流的一部分来使用 dockercommit ,因此,知道它还有别的用途也挺好。

我们发现,当我们需要通过一系列棘手的命令来配置应用时,这会是一个很有用的技巧。提交该容器时,一旦成功,它会记录我们的bash会话的历史,这也意味着通过一系列步骤重新恢复系统的状态成为可能。这可以节省大量的时间!而且,这在正实验一个新功能而又不太确定是否完工,或者当重现出一个漏洞然后想尽量确保能够回到那个挂掉的状态时,也很有用处。

毋庸置疑,比起一长串随机的字符串,肯定还有更好的办法来引用镜像。技巧16将会关注如何给这些镜像指定一个名称,以便能够更加轻松地引用它们。

results matching ""

    No results matching ""